
#define DUA_CORE

#include "dua_order.h"
#include "dua_lex.h"
#include "dua_compiler.h"

typedef struct {
	DuaObj obj;
	DuaOrder* order;
	unsigned cap;
	unsigned count;
	DuaState* D;
	DuaLex* lex;
}LoadCtx;

static int ORDER_TKS[][2] = {
	{TK_LET, ORDER_LET},
	{TK_VALUE, ORDER_VALUE},
	{TK_CAST, ORDER_CAST},
	{TK_ARITH, ORDER_ARITH},
	{TK_IF, ORDER_IF},
	{TK_ELSE, ORDER_ELSE},
	{TK_ENDIF, ORDER_ENDIF},
	{TK_PRINT, ORDER_PRINT},
	{0, 0},
};

static int ARITH_TKS[] = {
	'+', '-', '*', '/',
	'<', '>', TK_LE, TK_GE, TK_NE, TK_EQ, TK_SHL, TK_SHR, 0
};

static inline void
lexError(DuaState* D, DuaLex* lex, const char* msg) {
	char buf[128];
	int len = snprintf(buf, 128, "ctx lex error: line(%d), msg(%s)",
		lex->line, msg);
	duaRet(D, DUA_ERROR, buf);
}

static inline int64_t
checkInteger(DuaState* D, DuaLex* lex) {
	if (lex->token.tk != TK_NUMBER) {
		lexError(D, lex, "expect integer");
	}
	else if (lex->token.sem.type != DUA_TLONG) {
		lexError(D, lex, "expect integer");
	}
	return lex->token.sem.l;
}

static inline int
isArithToken(DuaState* D, DuaLex* lex) {
	int i = 0;
	int tk = lex->token.tk;
	while(ARITH_TKS[i]) {
		if (ARITH_TKS[i ++] == tk) {
			return 1;
		}
	}
	return 0;
}


static void
nextTarget( LoadCtx* ctx, DuaTarget* target) {
	duaNextLex(ctx->D, ctx->lex);
	DuaToken* tk = &ctx->lex->token;
	switch (tk->tk) {
	case '$':
		target->t = TARGET_TEMP;
		duaNextLex(ctx->D, ctx->lex);
		target->index = (int32_t)checkInteger(ctx->D, ctx->lex);
		break;
	case '@':
		target->type = TARGET_CONST;
		duaNextLex(ctx->D, ctx->lex);
		target->index = (int32_t)checkInteger(ctx->D, ctx->lex);
		break;
	case TK_NAME:
		target->type = TARGET_NAME;
		target->name = tk->sem.ts;
		break;
	default:
		lexError(ctx->D, ctx->lex, "unknow token");
		break;
	}
	duaNextLex(ctx->D, ctx->lex);
}

static inline void
pushOrder( LoadCtx* ctx, DuaOrder* order) {
	if (ctx->count >= ctx->cap) {
		unsigned cap = ctx->cap * 2;
		ctx->order = duaRelloc(ctx->D, ctx->order,
			sizeof(DuaOrder) * ctx->cap, sizeof(DuaOrder) * cap);
		ctx->cap = cap;
	}
	ctx->order[ctx->count++] = *order;
}

static void
pushK(LoadCtx* ctx) {
	duaNextLex(ctx->D, ctx->lex);
	DuaOrder order = { 0 };
	order.order = ORDER_K;
	duaLexValue(ctx->D, ctx->lex, &order.res.value);
	pushOrder(ctx, &order);
}

static void
parseOrder(LoadCtx* ctx) {
	DuaState* D = ctx->D;
	DuaLex* lex = ctx->lex;
	duaNextLex(D, lex);
	DuaToken* tk = &ctx->lex->token;
	int i = 0;
	int32_t op = -1;
	while (ORDER_TKS[i][0]) {
		if (ORDER_TKS[i][0] == tk->tk) {
			op = ORDER_TKS[i][1];
			break;
		}
		i++;
	}
	if (op == -1) {
		lexError(D, lex, "unknow bytecode token");
	}
	DuaOrder order = {0};
	order.order = op;
	switch (op) {
	case ORDER_LET:
	case ORDER_VALUE:
		nextTarget(ctx, &order.res);
		if (tk->tk != '=') {
			lexError(D, lex, "expect '='");
		}
		nextTarget(ctx, &order.v1);
		break;
	case ORDER_ARITH:
		nextTarget(ctx, &order.res);
		if (tk->tk != '=') {
			lexError(D, lex, "expect '='");
		}
		nextTarget(ctx, &order.v1);
		if (!isArithToken(D, lex)) {
			lexError(D, lex, "expect arith token");
		}
		order.tk = tk->tk;
		nextTarget(ctx, &order.v2);
		break;
	case ORDER_IF:
		nextTarget(ctx, &order.res);
		break;
	case ORDER_ELSE:
	case ORDER_ENDIF:
		duaNextLex(D, lex);
		break;
	case ORDER_PRINT:
		nextTarget(ctx, &order.res);
		break;
	default:
		lexError(D, lex, "unknow bytecode token");
		break;
	}
	pushOrder(ctx, &order);
}

static void
printValue(DuaValue* v) {
	switch (v->type) {
	case DUA_TINT:
		printf("%d:%s", v->i, "int");
		break;
	case DUA_TLONG:
		printf("%lld:%s", v->l, "long");
		break;
	case DUA_TFLOAT:
		printf("%f:%s", v->f, "float");
		break;
	case DUA_TDOUBLE:
		printf("%lf:%s", v->d, "double");
		break;
	case DUA_TSTRING:
		printf("\"%s\"", v->ts->s);
		break;
	}
}

static void
printTarget(DuaTarget* target) {
	switch (target->t) {
	case TARGET_CONST:
		printf("@%d", target->index);
		break;
	case TARGET_TEMP:
		printf("$%d", target->index);
		break;
	case TARGET_NAME:
		printf("%s", target->name->s);
		break;
	}
}


static void
printCode(LoadCtx* ctx) {
	DuaOrder* order;
	unsigned kIndex = 0;
	for (unsigned i = 0; i < ctx->count; i++) {
		order = &ctx->order[i];
		switch (order->order) {
		case ORDER_K:
			printf("= ");
			printValue(&order->res.value);
			printf("	//%d\n", kIndex);
			break;
		case ORDER_LET:
			printf(".let ");
			printTarget(&order->res);
			printf(" = ");
			printTarget(&order->v1);
			printf("\n");
			break;
		case ORDER_ARITH:
			printf(".arith ");
			printTarget(&order->res);
			printf(" = ");
			printTarget(&order->v1);
			printf(" %c ", order->tk);
			printTarget(&order->v2);
			printf("\n");
			break;
		case ORDER_IF:
			printf(".if ");
			printTarget(&order->res);
			printf("\n");
			break;
		case ORDER_ELSE:
			printf(".else\n");
			break;
		case ORDER_ENDIF:
			printf(".endif\n");
			break;
		case ORDER_PRINT:
			printf(".print ");
			printTarget(&order->res);
			printf("\n");
			break;
		}
	}
}

extern DuaProto*
duaLoadOrder(DuaState* D, const char* str, size_t len) {
	LoadCtx* ctx = duaNewObj(D, 0, sizeof(LoadCtx));
	ctx->cap = 32;
	ctx->count = 0;
	ctx->order = duaMalloc(D, sizeof(DuaOrder) * ctx->cap);

	DuaLex* lex = duaNewLex(D, duaNewStringLen(D, str, len));
	ctx->lex = lex;
	ctx->D = D;
	duaNextLex(D, lex);
	DuaToken* tk = &lex->token;
	while (tk->tk != TK_EOS) {
		switch (tk->tk) {
		case '=':
			pushK(ctx);
			break;
		case '.':
			parseOrder(ctx);
			break;
		default:
			lexError(D, lex, "unknow token");
			break;
		}
	}
	printCode(ctx);
	return duaCompile(D, ctx->order, ctx->count);
}